from flask import request, render_template, flash, redirect, url_for
import pandas as pd
from prophet import Prophet
from datetime import datetime, timedelta
import numpy as np
import io
from collections import defaultdict
import re
import matplotlib
import matplotlib.pyplot as plt
import base64
import random

def single_predict_view():
    product_info = None
    table = None
    metrics = None
    plot_url = None
    params = {'days': 30, 'current_stock': 100, 'lead_time': 15,'return_time':10, 'safety_factor': 1.2, 'return_rate':0.0,'incremental_activity':0}
    show_result = False
    daily_results = None
    summary = None
    if request.method == 'POST':
        file = request.files.get('file')
        days = int(request.form.get('days', 30))
        current_stock = float(request.form.get('current_stock', 100))
        lead_time = int(request.form.get('lead_time', 15))
        return_time = int(request.form.get('return_time', 10))
        safety_factor = float(request.form.get('safety_factor', 1.2))
        return_rate = float(request.form.get('return_rate', 0.0))
        incremental_activity = float(request.form.get('incremental_activity', 0))

        params = {'days': days, 'current_stock': current_stock, 'lead_time': lead_time,
                  'return_time':return_time, 'safety_factor': safety_factor, 'return_rate': return_rate,'incremental_activity': incremental_activity}
        # print("params", params)
        if not file:
            flash('请上传文件')
            return redirect(request.url)
        try:
            if file.filename.endswith('.csv'):
                df = pd.read_csv(file)
                product_id = get_product_id(file.filename)
            elif file.filename.endswith(('.xlsx','.xls')):
                df = pd.read_excel(file)
                product_id = get_product_id(file.filename)
            else:
                flash('仅支持CSV或Excel文件')
                return redirect(request.url)
        except Exception as e:
            flash(f'文件读取失败: {e}')
            return redirect(request.url)
        # 字段标准化
        if '统计日期' in df.columns and '支付件数' in df.columns:
            df = df[['统计日期', '支付件数']].rename(columns={'统计日期': 'ds', '支付件数': 'y'})
        elif '时间' in df.columns and '支付件数' in df.columns:
            df = df[['时间', '支付件数']].rename(columns={'时间': 'ds', '支付件数': 'y'})
        elif 'ds' in df.columns and 'y' in df.columns:
            df = df[['ds', 'y']]
        else:
            flash('文件需包含"统计日期"和"支付件数"字段')
            return redirect(request.url)
        df = preprocess_data(df)
        # print("预处理后的数据：",df)
        # 商品基本信息
        product_info = {
            '商品名称': request.form.get('product_name', '商品名称'),
            '商品ID': product_id,
            '数据区间': f"{df['ds'].min().strftime('%Y-%m-%d')} ~ {df['ds'].max().strftime('%Y-%m-%d')}",
            '总记录数': len(df)
        }
        max_ds = df['ds'].max().strftime('%Y-%m-%d')
        # print("111111111",max_ds)
        # 计算历史数据的最大最小值
        max_val = df['y'].max()
        min_val = max(df['y'].min(), 1)  # 避免零值


        # 使用逻辑增长，设置上限为历史最大值的倍数
        df['cap'] = max_val * 1.4
        df['floor'] = 0
        # Prophet训练
        try:
            model = Prophet(
                # 季节性设置
                yearly_seasonality=False,
                weekly_seasonality=False,
                daily_seasonality=False,

                # 趋势和变化点设置 - 增强局部趋势捕捉
                changepoint_prior_scale=0.15,  # 提高灵敏度以捕捉更多变化点
                changepoint_range=0.95,  # 允许在数据后期设置变化点
                n_changepoints=15,  # 增加变化点数量

                # 节假日设置
                # holidays_prior_scale=5.0,
                # holidays=holidays,

                # 其他重要参数
                seasonality_mode='additive',
                uncertainty_samples=1000,
                mcmc_samples=0,

                # 新增参数以增强局部趋势捕捉
                growth='logistic',  # 使用线性增长（默认）
                seasonality_prior_scale=0.001,  # 降低季节性影响，突出趋势


            )

            # model.add_country_holidays(country_name='CN')
            model.fit(df)
            # 预测期扩展以覆盖备货周期和退货周期
            future = model.make_future_dataframe(periods=days + max(lead_time, return_time) * 2)
            # future = model.make_future_dataframe(periods=days)
            # ====== 新增：计算周期阶段划分 ======
            # 计算每个阶段的长度（使用整数除法处理不能被均分的情况）
            rising_days = days // 3
            print("rising_days", rising_days)
            peak_days = days // 3
            decline_days = days - rising_days - peak_days

            # 设置阶段边界
            rising_end = rising_days
            peak_end = rising_days + peak_days

            # 阶段需求统计
            phase_demand = {
                'rising': 0,
                'peak': 0,
                'decline': 0
            }

            # 初始化阶段名称
            phase_names = {
                'rising': '上升期',
                'peak': '爆发期',
                'decline': '衰退期'
            }
            # ====== 阶段计算结束 ======

            future = future[future['ds'] >= max_ds]  #去除历史数据
            print("future", future)
            print("future", future.at[len(df),'ds'])
            # 获取关键日期点
            start_date = future['ds'].min()  # 未来数据开始日期
            rising_end_date = start_date + pd.Timedelta(days=rising_days)
            peak_end_date = rising_end_date + pd.Timedelta(days=peak_days)
            print("peak_end_date", peak_end_date)
            decline_end_date = peak_end_date + pd.Timedelta(days=decline_days+1)
            print("decline_end_date", decline_end_date)

            # 创建时间区间掩码
            peak_period = (future['ds'] >= rising_end_date) & (future['ds'] <= peak_end_date)
            decline_period = (future['ds'] > peak_end_date) & (future['ds'] <= decline_end_date)
            other_period = future['ds'] < rising_end_date



            # 生成 [1, 2] 范围内的随机小数（包含1和2）
            # 根据时间段设置cap值
            # import numpy as np

            # 上升期
            future.loc[other_period, 'cap'] = max_val * np.random.uniform(1, 1.5, other_period.sum())
            # 爆发期
            future.loc[peak_period, 'cap'] = max_val * np.random.uniform(1.5, 2.5, peak_period.sum())
            # 衰退期
            future.loc[decline_period, 'cap'] = max_val * np.random.uniform(0.5, 1.0, decline_period.sum())


            # 统一设置floor
            future['floor'] = 0
            forecast = model.predict(future)
            # forecast_future = forecast[forecast['ds'] > datetime.now()].copy()
            forecast_future = forecast[forecast['ds'] > max_ds].copy()

            #预测出的需求与退货率相乘
            # forecast_future['yhat'] = forecast_future['yhat']*(1-return_rate)

            #退货数
            # return_goods = forecast_future['yhat']*return_rate

            # print("forecast_future", forecast_future)

            if forecast_future.empty:
                flash('无法生成未来预测')
                return redirect(request.url)
            # 补货模拟
            daily_results = []
            stock_level = float(current_stock)


            incoming_deliveries = defaultdict(int)
            # print('incoming_deliveries', incoming_deliveries)
            return_deliveries = defaultdict(int)
            # print('return_deliveries', return_deliveries)

            full_forecast_dict = forecast_future.set_index('ds')['yhat'].to_dict()
            sim_dates = forecast_future['ds'].head(days)

            #活动增量初始化到总销量
            sum_predicted_demand = incremental_activity
            for i, date in enumerate(sim_dates):
                # ====== 新增：确定当前日期所处的阶段 ======
                if i < rising_end:
                    current_phase = 'rising'
                elif i < peak_end:
                    current_phase = 'peak'
                else:
                    current_phase = 'decline'
                # ====== 阶段判断结束 ======
                # === 退货到达处理 ===
                #货物退货处理
                return_goods = return_deliveries.pop(date.date(), 0)
                stock_level += return_goods
                print('return_goods:', return_goods)
                # === 货物到达处理 ===
                # # 检查并处理当天到达的补货
                delivery_arrived = incoming_deliveries.pop(date.date(), 0)
                # print('delivery_arrived', delivery_arrived)
                stock_level += delivery_arrived

                stock_on_order = sum(incoming_deliveries.values())+sum(return_deliveries.values())

                print('stock_on_order:', stock_on_order)

                predicted_demand = max(0, float(np.round(full_forecast_dict.get(date, 0))))

                #售卖周期总销量+活动增量
                sum_predicted_demand += predicted_demand
                #实际退后销量
                # re_predicted_demand = sum_predicted_demand*(1-return_rate)

                # === 计算当天产生的退货 ===
                return_qty = predicted_demand * return_rate
                if return_qty > 0:
                    # 安排退货在return_time天后到达
                    return_arrival_date = date + timedelta(days=return_time)
                    return_deliveries[return_arrival_date.date()] += return_qty

                # 退货数
                # return_goods = predicted_demand*return_rate

                stock_before_sale = stock_level
                stock_after_sale = stock_before_sale - predicted_demand
                print("stock_after_sale:", stock_after_sale)

                # 记录阶段需求
                phase_demand[current_phase] += predicted_demand
                # 计算安全库存和再订货点
                lead_time_window_dates = pd.to_datetime([date + timedelta(days=d) for d in range(1, days + 1)])
                # print("lead_time_window_dates",lead_time_window_dates)
                demand_during_lead_time = sum(max(0, np.round(full_forecast_dict.get(d, 0))) for d in lead_time_window_dates)

                safety_stock_for_lead_time = demand_during_lead_time * (safety_factor - 1.0)

                # reorder_point = demand_during_lead_time + safety_stock_for_lead_time
                reorder_point = demand_during_lead_time
                print('reorder_point:', reorder_point)
                # 补货决策
                inventory_position = stock_after_sale + stock_on_order
                order_qty = 0
                suggestion = "无需补货"
                # 新增：在销售周期结束前的补货提前期内停止订货

                if inventory_position < reorder_point*(1-return_rate):
                    order_qty = max(0, int(np.ceil(reorder_point*(1-return_rate) - stock_after_sale)))
                    if order_qty > 0:
                        suggestion = f"建议补货 {order_qty} 件"
                        arrival_date = (date + timedelta(days=lead_time)).date()
                        incoming_deliveries[arrival_date] += order_qty
                status = "库存充足"
                if stock_after_sale <= 0:
                    status = "缺货"
                elif stock_after_sale < safety_stock_for_lead_time:
                    status = "库存紧张"
                daily_results.append({
                    'date': date.strftime('%Y-%m-%d'),
                    'predicted_demand': int(predicted_demand),
                    'stock_before': int(np.round(stock_before_sale)),
                    'stock_after': int(np.round(stock_after_sale)),
                    'safety_stock': int(np.round(safety_stock_for_lead_time)),
                    'reorder_point': int(np.round(reorder_point)),
                    'status': status,
                    'suggestion': suggestion,
                    'order_qty': order_qty,
                    'delivery_arrived':delivery_arrived,
                    'return_generated': int(np.round(return_qty)),  # 新增当天生成的退货
                    'return_arrived': int(return_goods),  # 当天到达的退货
                    'stock_on_order': int(np.round(stock_on_order)),
                    # ====== 新增：添加阶段信息 ======
                    'phase': current_phase,
                    'phase_name': phase_names[current_phase]
                    # ====== 阶段信息添加结束 ======
                })
                stock_level = stock_after_sale
                # ====== 新增：计算阶段需求占比 ======
                total_demand = sum(phase_demand.values())
                phase_percentages = {}
                for phase, demand in phase_demand.items():
                    if total_demand > 0:
                        percentage = (demand / total_demand) * 100
                        phase_percentages[f'{phase}_percentage'] = round(percentage, 1)
                    else:
                        phase_percentages[f'{phase}_percentage'] = 0.0
                # ====== 阶段需求统计结束 ======

            summary = {
                'total_demand': int(sum(d['predicted_demand'] for d in daily_results) + incremental_activity),
                're_predicted_demand':int((sum(d['predicted_demand'] for d in daily_results) + incremental_activity)*(1-return_rate)),
                'total_orders': int((sum(d['predicted_demand'] for d in daily_results) + incremental_activity)*(1-return_rate))-int(current_stock),
                'model_type': '单商品Prophet模型',
                'product_name': product_info['商品名称'],
                'product_id': product_info['商品ID']
            }
            # 评估指标
            try:
                from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
                hist_pred = forecast.loc[:len(df)-1, 'yhat']
                hist_true = df['y'].values
                r2 = r2_score(hist_true, hist_pred)
                mae = mean_absolute_error(hist_true, hist_pred)
                rmse = mean_squared_error(hist_true, hist_pred, squared=False)
                mape = (abs((hist_true - hist_pred) / (hist_true + 1e-8))).mean() * 100
                metrics = {'R2': round(r2,4), 'MAE': round(mae,2), 'RMSE': round(rmse,2), 'MAPE': f'{mape:.2f}%'}
            except Exception:
                metrics = None
            # 生成趋势图
            # print("预测的数据",forecast)
            try:

                matplotlib.use('Agg')
                matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'Microsoft YaHei']
                matplotlib.rcParams['axes.unicode_minus'] = False

                fig, ax = plt.subplots(figsize=(10,3))
                ax.plot(df['ds'], df['y'], label='历史销量', marker='o')
                ax.plot(forecast['ds'], forecast['yhat'], label='预测', color='#667eea')
                ax.fill_between(forecast['ds'], forecast['yhat_lower'], forecast['yhat_upper'], color='#c3cfe2', alpha=0.3, label='置信区间')
                ax.legend()
                ax.set_title('历史与未来销量趋势')
                ax.set_xlabel('日期')
                ax.set_ylabel('销量')
                fig.tight_layout()
                buf = io.BytesIO()
                plt.savefig(buf, format='png')
                buf.seek(0)
                plot_url = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('utf-8')
                plt.close(fig)
            except Exception as e:
                plot_url = None
                print(f'生成图表失败: {str(e)}')
            show_result = True
        except Exception as e:
            flash(f'模型训练或预测失败: {e}')
            return redirect(request.url)
    return render_template('single_predict_analysis.html',
                             product_info=product_info, table=daily_results, metrics=metrics,
                              plot_url=plot_url, params=params, show_result=show_result, summary=summary)

def get_product_id(file_name):
    match = re.search(r'(\d{12})', file_name)
    if match:
        # print(f"提取的商品ID: {match.group(1)}")  # 输出: 12345
        return  match.group(1)
    else:
        return "未找到商品ID"

#数据预处理
def preprocess_data(df):
    # 1. 日期格式处理
    try:
        # 尝试自动解析日期
        df['ds'] = pd.to_datetime(
            df['ds'],
            format='%Y年%m月%d日',
            errors='coerce'
        )

        # 如果全部解析失败，尝试中文格式转ISO格式
        if df['ds'].isna().all():
            def chinese_date_to_iso(s):
                m = re.match(r'(\d{4})年(\d{1,2})月(\d{1,2})日', str(s))
                if m:
                    return f"{m.group(1)}-{int(m.group(2)):02d}-{int(m.group(3)):02d}"
                return s

            df['ds'] = df['ds'].apply(chinese_date_to_iso)
            df['ds'] = pd.to_datetime(df['ds'], errors='coerce')

        # 移除无效日期
        df = df.dropna(subset=['ds'])
        df = df.sort_values('ds')

        # 检查日期连续性
        date_diff = df['ds'].diff().dt.days
        if any(date_diff[1:] > 1):  # 跳过第一个NaN
            flash('警告：日期存在间隔，可能导致预测偏差')

    except Exception as e:
        flash(f'日期处理失败: {e}')
        return None

    # 2. 数值处理
    try:
        # 转换数值类型
        df['y'] = pd.to_numeric(df['y'], errors='coerce')

        # 检查数据量
        if len(df) < 1:
            flash('数据量过少，建议上传10条以上的连续历史数据')
            return None
    except Exception as e:
        flash(f'数值处理失败: {e}')
        return None

    # 3. 异常值检测与处理
    try:
        # 方法1: IQR(四分位距)方法检测静态异常值
        Q1 = df['y'].quantile(0.25)
        Q3 = df['y'].quantile(0.75)
        IQR = Q3 - Q1
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR

        # 方法2: 基于移动标准差检测时间序列异常值
        window_size = max(3, min(7, len(df) // 4))  # 动态窗口大小
        df['rolling_mean'] = df['y'].rolling(window=window_size, min_periods=1).mean()
        df['rolling_std'] = df['y'].rolling(window=window_size, min_periods=1).std().fillna(0)
        std_threshold = 2.5  # 标准差倍数阈值

        # 识别异常值
        df['is_outlier'] = False
        df['is_outlier'] |= (df['y'] < lower_bound) | (df['y'] > upper_bound)
        df['is_outlier'] |= (abs(df['y'] - df['rolling_mean']) > std_threshold * df['rolling_std'])

        # 处理异常值 (保留原始数据，创建新列处理)
        df['y_processed'] = df['y']

        # 对于异常值，使用线性插值或前后均值替代
        for i in range(len(df)):
            if df.iloc[i]['is_outlier']:
                # 尝试使用前后点插值
                if i > 0 and i < len(df) - 1:
                    prev_val = df.iloc[i - 1]['y_processed']
                    next_val = df.iloc[i + 1]['y_processed']
                    df.at[i, 'y_processed'] = (prev_val + next_val) / 2
                # 如果首尾点异常，使用最近的非异常值
                elif i == 0 and len(df) > 1:
                    df.at[i, 'y_processed'] = df.iloc[1]['y_processed']
                elif i == len(df) - 1 and len(df) > 1:
                    df.at[i, 'y_processed'] = df.iloc[-2]['y_processed']

        # 记录异常值数量
        outlier_count = df['is_outlier'].sum()
        if outlier_count > 0:
            flash(f'检测到{outlier_count}个异常值已处理')

        # 移除临时列
        df = df.drop(columns=['rolling_mean', 'rolling_std', 'is_outlier'])

        return df

    except Exception as e:
        flash(f'异常值处理失败: {e}')
        return df[['ds', 'y']]  # 返回原始数据作为后备

